stack 6/7: triage the overnight PRs and fix the #955 defects they found - #973
Conversation
#967 found two real defects in my own #955 code and both verify at runtime: a Team account with a monthly window could never recover because the predicate picked its window by plan name while the parser picks by window duration, and the probe's own token refresh was mistaken for an external credential replacement. #963 and #965 both claim #962; #965 wins because it inherits from the row it actually replaces rather than recomputing config hints, and because #963 rewrites an existing regression contract to justify a broader change. #966 is a fifth design for #914 that survives two of the four prior falsifications but not all: mixed 5xx-then-rejection still loses the attributable failure, and five newly-classified sidecar paths keep default redirects, so a credential-visible 307 to a dead host still reads as neutral.
…ned refresh generations Addresses the two unresolved Codex review threads on #955: - isCompleteCodexQuotaRecoverySnapshot() required weeklyPercent for every non-Go/Free plan by plan name, but the parser classifies windows by duration: a Team response with an explicitly monthly primary window parses to monthlyPercent only, so those accounts could never recover early and stayed cooled until their predicted expiry. - settleCodexQuotaRecoveryProbe() required the claim-time credential generation to match exactly. A probe-owned token refresh inside getValidCodexToken() advances the generation by one before WHAM completes, so a successful fresh reading was rejected and the account waited another probe interval. replacedAt is preserved by refresh and stamped by external replacement, so it fences the +1 transition. (cherry picked from commit 79d2164)
(cherry picked from commit c5565d0)
(cherry picked from commit 3f7e4cf)
(cherry picked from commit 46756f5)
(cherry picked from commit a366934)
(cherry picked from commit 9ba66a1)
… recovers a weekly plan #967 correctly found that requiring weeklyPercent by plan name stranded Team accounts whose primary window is explicitly monthly. Its remedy — accept any window the parser wrote — was too permissive in the other direction: a tertiary-only response also writes monthlyPercent, describes a different period, and says nothing about the weekly quota that actually gates the account, so it could clear a cooldown on a reading of the wrong window. parseUsageQuota() now records monthlyIsPrimaryWindow when the monthly value came from an explicitly-monthly PRIMARY window, and recovery requires that provenance before accepting monthly-only evidence for a weekly-quota plan. Go/Free are unaffected: the monthly window governs them either way. The two shapes were previously indistinguishable — both parsed to {monthlyPercent} with no way to tell which window produced it.
Recovery reads freshQuota directly, so this is not on its path today — but setAccountQuotaFromParsed() copies fields one by one, and a cached snapshot that kept monthlyPercent while dropping monthlyIsPrimaryWindow would look exactly like tertiary-only data to any future reader. A flag that silently fails to persist makes the guard decorative, and that failure would be invisible rather than loud.
…er parser too The audit noted the cache round trip was untested — so the guard could have been silently reduced to decoration by a later refactor. The test now asserts the flag survives setAccountQuotaFromParsed(), and ablating that copy fails it. parseUpstreamQuotaHeaders() recognizes the same explicitly-monthly primary window and now records the same provenance. It is not on the recovery path today, but two parsers disagreeing about what a bare monthlyPercent means is the kind of divergence that surfaces later as an unexplainable bug.
…enance Nine tests asserted parseUsageQuota()/getAccountQuota() output with toEqual, so the new monthlyIsPrimaryWindow field failed them on shape while every value was unchanged. Each expectation now states which side of the distinction it is on, which is the thing those tests were already about: - explicit-monthly PRIMARY windows carry the flag - the Go/Free thirtyDayOnly branch does not (recovery never consults it there) - a tertiary-sourced monthly value does not, which is the case that made the guard necessary - a credits-only refresh preserving prior usage does not The cached monthly-A snapshot now carries it too, proving propagation through setAccountQuotaFromParsed() rather than only asserting the parse.
The last copy site of the same class: an unrelated weekly update rebuilt the record and carried monthlyPercent forward without its provenance, silently downgrading a proven explicit-primary reading to unproven. The mirror case matters as much — a caller-supplied monthly value arrives with no window information, so it must REPLACE the proof rather than inherit it. Both directions are now pinned, and ablating the carry fails the test.
📝 WalkthroughWalkthroughThe change records overnight pull-request triage decisions and implements four fixes: Gemini tool-choice serialization, provider catalog metadata inheritance, quota provenance tracking, and credential-refresh-aware quota recovery. ChangesGoogle tool-choice handling
Provider catalog enrichment
Quota recovery and credential fencing
Overnight triage record
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant Caller
participant GoogleAdapter
participant GeminiAPI
Caller->>GoogleAdapter: Build request with tools and tool_choice
GoogleAdapter->>GoogleAdapter: Map tool_choice to functionCallingConfig
GoogleAdapter->>GeminiAPI: Send tools and optional toolConfig
GeminiAPI-->>Caller: Return configured model response
Possibly related PRs
Suggested labels: Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 ESLint
ESLint install failed. For unrecoverable errors, disable the tool in CodeRabbit configuration. Comment |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 74c4f765e6
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| **Falsification 3 survives on five expanded surfaces.** Manual redirects were | ||
| added only to Responses and Compact; the five sidecar paths #966 newly | ||
| classifies still use default-follow fetch, so a credential-bearing sidecar that | ||
| receives a 307 to a dead host is misclassified as neutral — after the origin | ||
| already read the `Authorization` header: |
There was a problem hiding this comment.
Move unfixed security triage out of the devlog
This tracked _plan note publishes the exact credential-bearing redirect counterexample and live reproduction while the document later states that #914 remains open and #966 is not mergeable. Pushing this commit would disclose bypass reasoning and reproduction details before the fix ships, and deleting them later would not remove them from Git history; keep this material in .tmp/ or mktemp scratch space and publish only the fixed outcome.
AGENTS.md reference: AGENTS.md:L61-L69
Useful? React with 👍 / 👎.
| const enrichedByName = new Map(activeProviders); | ||
| // Provider-derived rows keyed by their Codex-facing slug: a custom override replaces the row | ||
| // with the same slug below, so that row's provider capability metadata is the inheritance source. | ||
| const replacedByRoutedSlug = new Map(all.map(model => [routedSlug(model.provider, model.id), model])); |
There was a problem hiding this comment.
Key inherited metadata by native model identity
When a provider exposes both a slash-containing ID such as foo/bar and the literal ID foo-bar, routedSlug() maps both to the same key. This map therefore retains whichever row appears last, so a custom override for the exact foo-bar model can inherit the context, modalities, or reasoning metadata of foo/bar, even though routing gives the literal native ID precedence. Index the inheritance source by provider plus raw model ID, or explicitly reject ambiguous encoded-slug collisions.
Useful? React with 👍 / 👎.
Stack navigation — 7 layers, review and merge bottom-up
Each layer targets the branch below it, so its diff only makes sense on that base — Note for the merge sequence: retargeting a child after its parent merges emits an |
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@devlog/_plan/260804_overnight_triage/000_dispositions.md`:
- Line 133: Update the adapter description in the disposition entry for `#968` to
capitalize “Google” in the phrase “Google adapter,” leaving the surrounding text
unchanged.
- Around line 79-80: Escape the leading PR-number references in the affected
paragraphs, including `#962`, `#965`, `#963`, and `#966`, by escaping the hash or using
inline code formatting so markdownlint MD018 is resolved while the rendered text
remains unchanged.
In `@src/codex/routing.ts`:
- Around line 522-536: Replace the timestamp-based credentialReplacedAt lineage
marker with a persistent opaque ID or monotonic external-replacement version,
and update the generation fence around isCodexAccountGenerationLive to compare
that collision-free marker. Preserve the marker in
saveCodexAccountCredentialIfGeneration and carry it through src/codex/routing.ts
ranges 142-143, 450, 479, and 504-506. Add a fixed-clock test in
tests/codex-cooldown-recovery.test.ts ranges 171-197 proving same-millisecond
external replacement retains the cooldown, while keeping the probe-owned refresh
test and asserting /oauth/token is called, generation advances once, and the
marker is unchanged.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 661d7d1f-f467-4035-8922-901af918c4e0
📒 Files selected for processing (10)
devlog/_plan/260804_overnight_triage/000_dispositions.mdsrc/adapters/google.tssrc/codex/catalog/provider-fetch.tssrc/codex/quota.tssrc/codex/routing.tstests/codex-catalog.test.tstests/codex-cooldown-recovery.test.tstests/codex-routing.test.tstests/google-adapter.test.tstests/rate-limit-reset-credits.test.ts
| #962 is specifically about a custom row *replacing* a same-slug provider row. | ||
| #965 models exactly that: it indexes the rows deduplication will replace and |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Escape PR-number references at paragraph starts.
Lines [79]-[80], [84], [86], and [98] begin with # followed immediately by digits. markdownlint reports MD018 for these lines. Escape the hash or use inline code formatting so the rendered text remains #962, #965, #963, and #966.
Proposed fix
-#962 is specifically about a custom row
+\`#962` is specifically about a custom row
-#965 models exactly that
+\`#965` models exactly that
-#963 instead recomputes `catalogHintsFromProviderConfig()`
+\`#963` instead recomputes `catalogHintsFromProviderConfig()`
-#962 requires.
+\`#962` requires.
-#966 targets `#914`
+\`#966` targets `#914`Also applies to: 84-86, 98-98
🧰 Tools
🪛 markdownlint-cli2 (0.23.1)
[warning] 79-79: No space after hash on atx style heading
(MD018, no-missing-space-atx)
[warning] 80-80: No space after hash on atx style heading
(MD018, no-missing-space-atx)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@devlog/_plan/260804_overnight_triage/000_dispositions.md` around lines 79 -
80, Escape the leading PR-number references in the affected paragraphs,
including `#962`, `#965`, `#963`, and `#966`, by escaping the hash or using inline code
formatting so markdownlint MD018 is resolved while the rendered text remains
unchanged.
Source: Linters/SAST tools
|
|
||
| ## The rest | ||
|
|
||
| **#968** (@DevMello) — the google adapter dropped `tool_choice` entirely: `none`, |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Capitalize Google.
Write Google adapter at Line [133]. Google is a proper name, and static analysis flags the lowercase form.
🧰 Tools
🪛 LanguageTool
[uncategorized] ~133-~133: “Google” is a proper noun and needs to be capitalized.
Context: ...## The rest #968 (@DevMello) — the google adapter dropped tool_choice entirely:...
(A_GOOGLE)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@devlog/_plan/260804_overnight_triage/000_dispositions.md` at line 133, Update
the adapter description in the disposition entry for `#968` to capitalize “Google”
in the phrase “Google adapter,” leaving the surrounding text unchanged.
Source: Linters/SAST tools
| const currentRecord = readCodexAccountRecord(claim.accountId); | ||
| const proofGeneration = proof.credentialGeneration; | ||
| // A probe-owned token refresh (getValidCodexToken) advances the credential generation by | ||
| // exactly one while preserving `replacedAt`; an external credential replacement bumps the | ||
| // generation too but stamps a fresh `replacedAt`. Accept the +1 transition only when the | ||
| // claim-time lineage is intact AND the generation the fresh quota was proven under is live. | ||
| const generationFenced = proofGeneration !== undefined | ||
| && (proofGeneration === claim.credentialGeneration | ||
| ? isCodexAccountGenerationLive(claim.accountId, proofGeneration) | ||
| : proofGeneration === claim.credentialGeneration + 1 | ||
| && currentRecord?.replacedAt === claim.credentialReplacedAt | ||
| && isCodexAccountGenerationLive(claim.accountId, proofGeneration)); | ||
| const fenced = (health.cooldownGeneration ?? 0) === claim.cooldownGeneration | ||
| && (health.probeLeaseGeneration ?? 0) === claim.cooldownGeneration | ||
| && claim.credentialGeneration === proof.credentialGeneration | ||
| && isCodexAccountGenerationLive(claim.accountId, claim.credentialGeneration); | ||
| && generationFenced; |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -eu
printf '%s\n' '--- routing symbols and relevant sections ---'
rg -n -C 8 'credentialReplacedAt|replacedAt|credentialGeneration|generationFenced|saveCodexAccountCredential|isCodexAccountGenerationLive' src/codex/routing.ts src tests/codex-cooldown-recovery.test.ts
printf '%s\n' '--- credential record definitions and writes ---'
rg -n -C 8 'interface .*Codex|type .*Codex|replacedAt|credentialGeneration|saveCodexAccountCredential' src testsRepository: lidge-jun/opencodex
Length of output: 50376
🏁 Script executed:
#!/bin/bash
set -eu
printf '%s\n' '--- routing.ts targeted ranges ---'
sed -n '120,155p;430,545p' src/codex/routing.ts
printf '%s\n' '--- account-store declarations and writes ---'
rg -n -C 12 'replacedAt|generation|function saveCodexAccountCredential|const saveCodexAccountCredential|saveCodexAccountCredentialIfGeneration|readCodexAccountRecord' src/codex/account-store.ts
printf '%s\n' '--- cooldown recovery tests around the supplied regression ---'
sed -n '130,225p' tests/codex-cooldown-recovery.test.tsRepository: lidge-jun/opencodex
Length of output: 26134
🏁 Script executed:
#!/bin/bash
set -eu
printf '%s\n' '--- recovery probe proof flow ---'
rg -n -C 14 'runCodexCooldownRecoveryProbes|getValidCodexToken|credentialGeneration|settleCodexQuotaRecoveryProbe|probe.*proof|proof:' src/codex/routing.ts
printf '%s\n' '--- account record type ---'
sed -n '1,45p' src/codex/account-store.ts
printf '%s\n' '--- all relevant replacement-marker tests ---'
rg -n -C 8 'replacedAt|credentialReplacedAt|external replacement|replacement.*cooldown|cooldown.*replacement' tests/codex* src/codex
printf '%s\n' '--- deterministic settlement model ---'
python3 - <<'PY'
from dataclasses import dataclass
`@dataclass`
class Claim:
generation: int
replaced_at: int | None
`@dataclass`
class Record:
generation: int
replaced_at: int | None
def generation_fenced(claim: Claim, record: Record, proof_generation: int) -> bool:
live = record.generation == proof_generation
return (
proof_generation == claim.generation and live
) or (
proof_generation == claim.generation + 1
and record.replaced_at == claim.replaced_at
and live
)
# Probe-owned refresh: generation advances, lineage marker is preserved.
claim = Claim(7, 1_700_000_000_000)
refresh = Record(8, 1_700_000_000_000)
assert generation_fenced(claim, refresh, 8)
# External replacement in the same millisecond: generation advances, but
# Date.now() produces the same marker, so the current predicate also accepts it.
same_ms_replacement = Record(8, 1_700_000_000_000)
assert generation_fenced(claim, same_ms_replacement, 8)
# A replacement in a different millisecond is rejected by the marker check.
different_ms_replacement = Record(8, 1_700_000_000_001)
assert not generation_fenced(claim, different_ms_replacement, 8)
print("probe-owned refresh accepted: yes")
print("same-millisecond external replacement accepted: yes")
print("different-millisecond external replacement rejected: yes")
PYRepository: lidge-jun/opencodex
Length of output: 21223
🏁 Script executed:
#!/bin/bash
set -eu
printf '%s\n' '--- existing replacement race test ---'
sed -n '255,305p' tests/codex-cooldown-recovery.test.ts
printf '%s\n' '--- recovery runner locations and implementation ---'
rg -l 'runCodexCooldownRecoveryProbes' src tests
rg -n -C 18 'runCodexCooldownRecoveryProbes|settleCodexQuotaRecoveryProbe\(' src tests
printf '%s\n' '--- deterministic settlement model ---'
python3 - <<'PY'
from dataclasses import dataclass
`@dataclass`
class Claim:
generation: int
replaced_at: object
`@dataclass`
class Record:
generation: int
replaced_at: object
def generation_fenced(claim, record, proof_generation):
live = record.generation == proof_generation
return (
(proof_generation == claim.generation and live)
or (
proof_generation == claim.generation + 1
and record.replaced_at == claim.replaced_at
and live
)
)
claim = Claim(7, 1700000000000)
# The intended probe-owned refresh preserves the marker.
refresh = Record(8, 1700000000000)
assert generation_fenced(claim, refresh, 8)
# An external replacement in the same millisecond receives the same Date.now()
# marker and passes the current +1 predicate.
same_ms_replacement = Record(8, 1700000000000)
assert generation_fenced(claim, same_ms_replacement, 8)
# A replacement in another millisecond is rejected by the current marker check.
different_ms_replacement = Record(8, 1700000000001)
assert not generation_fenced(claim, different_ms_replacement, 8)
print("probe-owned refresh accepted: yes")
print("same-millisecond external replacement accepted: yes")
print("different-millisecond external replacement rejected: yes")
PYRepository: lidge-jun/opencodex
Length of output: 40119
Use a collision-free credential-lineage marker.
saveCodexAccountCredential() assigns replacedAt with Date.now(). If an external replacement occurs in the same millisecond as the claim-time marker, generation N + 1 and the same replacedAt pass the +1 branch at src/codex/routing.ts:522-536. The probe can then clear a cooldown using quota from an external credential.
- Replace
credentialReplacedAtwith a persistent opaque lineage ID or monotonic external-replacement version. - Preserve the marker during
saveCodexAccountCredentialIfGeneration(). - Carry it through
src/codex/routing.ts:142-143,450,479, and504-506. - Add a fixed-clock test in
tests/codex-cooldown-recovery.test.tsthat retains the cooldown after same-millisecond replacement. - Keep the probe-owned refresh test. Assert that
/oauth/tokenis called, generation advances once, and the lineage marker is unchanged.
📍 Affects 2 files
src/codex/routing.ts#L522-L536(this comment)src/codex/routing.ts#L142-L143src/codex/routing.ts#L450-L450src/codex/routing.ts#L479-L479src/codex/routing.ts#L504-L506tests/codex-cooldown-recovery.test.ts#L171-L197
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/codex/routing.ts` around lines 522 - 536, Replace the timestamp-based
credentialReplacedAt lineage marker with a persistent opaque ID or monotonic
external-replacement version, and update the generation fence around
isCodexAccountGenerationLive to compare that collision-free marker. Preserve the
marker in saveCodexAccountCredentialIfGeneration and carry it through
src/codex/routing.ts ranges 142-143, 450, 479, and 504-506. Add a fixed-clock
test in tests/codex-cooldown-recovery.test.ts ranges 171-197 proving
same-millisecond external replacement retains the cooldown, while keeping the
probe-owned refresh test and asserting /oauth/token is called, generation
advances once, and the marker is unchanged.
Source: Path instructions
…ice repair Two overnight contributor PRs describe real defects the lidge-jun#951-lidge-jun#973 stack does not touch. This unit plans layer 7 as their reconstruction. lidge-jun#964 cannot be carried: five ids in its hand-written text-only list are natively image-capable per NVIDIA's own docs (inkling, minimax-m3, kimi-k2.6, step-3.7-flash, mistral-medium-3.5-128b). A false positive there is silent — the model can read the image, but the proxy substitutes another model's text description. Issue lidge-jun#956's own body carries two of the same errors, so reporter and author shared the premise. 010 inverts the design: maintain the 15 verified vision-capable ids and derive text-only as the complement, so an unclassified new model defaults to sidecar-on rather than to the bug being fixed. lidge-jun#970's premise is right but its diff is oversized: repairService() and 'ocx service repair' already exist here. 020 records the safety proof that matters — repair throws when not installed and the update path runs after 'ocx stop', but stop never deregisters on any of the three platforms. It also closes a hole lidge-jun#970 leaves: bin/ocx.mjs infers service presence from a possibly-stale marker, where repair would throw and lose the managed service. 030 sequences the bottom-up merge and issue closure, including the lidge-jun#954 security-review gate that can legitimately stop the queue.
Stack
6/6 — overnight PR triage
Base:
codex/915-cooldown-recovery-probe(#955)Summary
Nine PRs arrived overnight while this stack was in review. This layer carries the three that belong here, and records an evidence-backed disposition for the other six.
All carried commits keep their authors (
cherry-pick -x, patch-id verified identical).#967 found two real bugs in my #955, and both verify
Monthly-classified snapshots were rejected. My predicate picked the required window from the plan name; the parser picks it from the window duration. A Team account whose primary window is explicitly monthly parses to
monthlyPercentonly, so every successful fresh read was thrown away:That is the same failure #915 exists to fix, reintroduced for monthly-window plans — the third time this predicate has been wrong in the same direction.
The probe's own token refresh looked like a replacement.
getValidCodexToken()refreshes a near-expiry token mid-probe and advances the credential generation by one; my settle required an exact match, so a valid fresh reading was discarded and recovery waited another interval. Fenced onreplacedAt, which is preserved by a CAS refresh and stamped fresh by a real replacement.Where I disagreed with #967, and why
Its remedy for the first defect — accept whatever window the parser wrote — is too permissive in the other direction. A tertiary-only response also writes
monthlyPercent, describes a different period, and says nothing about the weekly quota that actually gates the account. The two were literally indistinguishable:So this adds provenance at the source instead of guessing at the sink:
parseUsageQuota()recordsmonthlyIsPrimaryWindowwhen the value came from an explicitly-monthly primary window, and recovery requires it before accepting monthly-only evidence for a weekly-quota plan. Go/Free are unaffected — the monthly window governs them either way.The flag is propagated through every copy site (
setAccountQuotaFromParsed,updateAccountQuota, the credits-only and weekly-preserving branches,parseUpstreamQuotaHeaders) and pinned by tests, because a flag that silently fails to persist makes the guard decorative.Why #963 loses to #965
Both claim #962. #962 is about a custom row replacing a same-slug provider row, and #965 models exactly that — it inherits from the row actually being replaced, so it also retains live
/modelsmetadata. #963 recomputes config hints for every custom row including unmatched ones, cannot retain discovered metadata, and rewritestests/catalog-vision-sidecar-modalities.test.tsfrom "no registry reasoning leaks onto an unmatched override" to expecting that leak while dropping threefetch should not be calledguards. Changing a test that encodes a deliberate prior decision, to make a broader change pass, is what decided it.Why #966 stays open
It is a genuine advance on the four previously-falsified #914 designs — real Bun labels, no hostname probing, manual redirects on the pool paths. But two falsifications survive, both reproduced live:
fetchWithTransientRetry()discards a prior 503 when a later attempt rejects, so a real account failure is recorded as neutral; and the five sidecar paths it newly classifies still use default-follow fetch, so a credential-visible 307 to a dead host reads as neutral after the origin has already seen theAuthorizationheader. It does supersede #922.Verification
bun x tsc --noEmit— exit 0bun run test— 7740 pass, 8 skip, 0 fail, 508 filesbun run privacy:scan— passedNine pre-existing
toEqualassertions needed updating for the new field — values unchanged, and each now states which side of the provenance distinction it is on.Audit
Four rounds, three FAIL. The reviewer caught the tertiary-only over-permissiveness, then two further copy sites of the same class. Every finding was reproduced at runtime before fixing.
Summary by CodeRabbit